/**
 * 
 */
package pacMan;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Scanner;

/**
 * Class: MazeBuilder
 * 
 * Singleton class that reads in the maze configuration file and creates all game objects
 * that we need to populate the game board as well as track the location of each object on the
 * game board.
 * 
 * @author 		Tomas Rivera
 * @version 	$Id$
 *
 */
public class MazeBuilder {
	
	//////////////////////////////////////////////////////////
	// Constructor methods
	//////////////////////////////////////////////////////////
	
	/*
	 * Instantiates a single copy of this class upon request.
	 * 
	 */
	public static MazeBuilder getInstance() {
		if(instance == null) {
			instance = new MazeBuilder();
		}
		
		return instance;
	}
	
	protected MazeBuilder() { }
	
	//////////////////////////////////////////////////////////
	// Public methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Static method that creates a Coordinate object that is centered at the specified
	 * game board 'tile'.
	 * 
	 * @param xGrid
	 * @param yGrid
	 * @param windowParams
	 * @return
	 */
	public static Coordinate createCoordinateObj(int xGrid, int yGrid, WindowParameters windowParams) {
		return new Coordinate((xGrid * windowParams.getPixelScale()) + windowParams.getPixelOffsetX(),
							  (yGrid * windowParams.getPixelScale()) + windowParams.getPixelOffsetY(),
							  windowParams.getWindowWidth(), windowParams.getWindowHeight());
	}
	
	/**
	 * Read configuration file for maze, and create ObjectLocationTracker that contains maze objects.
	 * 
	 * @param windowParams
	 * @param scoreTracker
	 * @param configFileName
	 * @param gridWidth
	 * @param gridHeight
	 * @return
	 */
	public ObjectLocationTracker createMaze(WindowParameters windowParams, ScoreTracker scoreTracker, String configFileName, int gridWidth, int gridHeight) {
		InputStream stream = this.getClass().getResourceAsStream(configFileName);
		System.out.println(stream.toString());
		InputStreamReader streamReader = new InputStreamReader(stream);
		BufferedReader in = new BufferedReader(streamReader);
		
		ObjectLocationTracker oltObj = new ObjectLocationTracker(gridWidth, gridHeight, windowParams);
		Wall wall = null;

		// Parse the maze file
		Scanner input = new Scanner(in);
		
		try {
			while (input.hasNext()) {
				if(input.hasNextInt()) {
					if(wall != null) {
						wall.addCoordinate(createCoordinateObj(input.nextInt(), input.nextInt(), windowParams));
					} else {
						System.err.println("Parsing error, missing start before first coordinate pair.");
						input.close();
						return null;
					}
				} else {
					String str = input.next();
					if(str.equals("start")) {
						wall = new Wall(createCoordinateObj(input.nextInt(), input.nextInt(), windowParams), windowParams);
					} else if(str.equals("lineend")) {
						addObstacleToGrid(wall, oltObj);
						wall = null;
					} else if(str.equals("rectend")) {
						wall.addCoordinate(wall.getObjectLocation());
						addObstacleToGrid(wall, oltObj);
						wall = null;
					} else if(str.equals("openboxend")) {
						addObstacleToGrid(wall, oltObj);
						wall.configureDoubleLine();
						wall = null;
					} else if(str.equals("filledlineend")) {
						addObstacleToGrid(wall, oltObj);
						wall = null;
					} else if(str.equals("pp")) {
						oltObj.addStationaryObject(ObjectLocationTracker.SSOType.ENERGIZER, input.nextInt(), input.nextInt());
					} else if(str.equals("nopelletstart")) {
						int rangeStartX = input.nextInt();
						int rangeStartY = input.nextInt();
						int rangeEndX = input.nextInt();
						int rangeEndY = input.nextInt();
						if(!input.next().equals("nopelletend")) {
							System.err.println("Invalid range specified.");
							System.exit(1);
						}
						
						if(rangeStartX == rangeEndX) {
							// Vertical line
							for(int i = rangeStartY; i <= rangeEndY; i++) {
								oltObj.addStationaryObject(ObjectLocationTracker.SSOType.EMPTY_LOCATION, rangeStartX, i);
							}
						} else {
							// Horizontal line
							for(int i = rangeStartX; i <= rangeEndX; i++) {
								oltObj.addStationaryObject(ObjectLocationTracker.SSOType.EMPTY_LOCATION, i, rangeStartY);
							}
						}
						
					} else if(str.equals("tunnelstart")) {
						int entrance_rangeStartX = input.nextInt();
						int entrance_rangeStartY = input.nextInt();
						int entrance_rangeEndX = input.nextInt();
						int entrance_rangeEndY = input.nextInt();
						int exit_rangeStartX = input.nextInt();
						int exit_rangeStartY = input.nextInt();
						int exit_rangeEndX = input.nextInt();
						int exit_rangeEndY = input.nextInt();
						if(!input.next().equals("tunnelend")) {
							System.err.println("Invalid range specified.");
							System.exit(1);
						}
						
						// Make sure the tunnel entrance & exit line up.
						if(entrance_rangeStartX != entrance_rangeEndX &&
							entrance_rangeStartX != exit_rangeStartX &&
							entrance_rangeStartX != exit_rangeEndX &&
							entrance_rangeStartY != entrance_rangeEndY &&
							entrance_rangeStartY != exit_rangeStartY &&
							entrance_rangeStartY != exit_rangeEndY)
						{
							System.err.println("Invalid tunnel parameters, entrance & exit do not line up.");
							System.exit(1);
						}
						
						if(entrance_rangeStartX == entrance_rangeEndX) {
							// Vertical line
							for(int i = entrance_rangeStartY; i <= entrance_rangeEndY; i++) {
								oltObj.addStationaryObject(ObjectLocationTracker.SSOType.TUNNEL_LOCATION, entrance_rangeStartX, i);
							}
						} else {
							// Horizontal line
							for(int i = entrance_rangeStartX; i <= entrance_rangeEndX; i++) {
								oltObj.addStationaryObject(ObjectLocationTracker.SSOType.TUNNEL_LOCATION, i, entrance_rangeStartY);
							}
						}
						
						if(exit_rangeStartX == exit_rangeEndX) {
							// Vertical line
							for(int i = exit_rangeStartY; i <= exit_rangeEndY; i++) {
								oltObj.addStationaryObject(ObjectLocationTracker.SSOType.TUNNEL_LOCATION, exit_rangeStartX, i);
							}
						} else {
							// Horizontal line
							for(int i = exit_rangeStartX; i <= exit_rangeEndX; i++) {
								oltObj.addStationaryObject(ObjectLocationTracker.SSOType.TUNNEL_LOCATION, i, exit_rangeStartY);
							}
						}
						
					}else {
						System.err.println(String.format("Invalid keyword found in file: %s.", str));
						input.close();
						return null;
					}
				}
			}
		} catch (NoSuchElementException e) {
			System.err.println("Parsing error.");
			input.close();
			return null;
			
		} catch (IllegalStateException e) {
			System.err.println("Scanner closed.");
			return null;
		}
		
		input.close();
		
		// Add normal pellets to all remaining locations.
		oltObj.fillGrid(ObjectLocationTracker.SSOType.PELLET);
		
		return oltObj;
	}
	
	//////////////////////////////////////////////////////////
	// Private methods
	//////////////////////////////////////////////////////////
	
	/*
	 * Takes the list of Coordinate objects that is defined for each Wall object, and
	 * uses that to mark all grid locations in the ObjectLocationTracker that the
	 * object spans.
	 *
	 */
	private void addObstacleToGrid(Wall wall, ObjectLocationTracker oltObj) {
		ArrayList<Coordinate> wallPoints = wall.getCoordinates();
		Iterator<Coordinate> iterator = wallPoints.iterator();
		Coordinate start = wall.getObjectLocation();
		while(iterator.hasNext()) {
			Coordinate end = iterator.next();
			/*System.out.printf("Obstacle (Wall): x1=%d y1=%d x2=%d y2=%d%n", 
					coordinateToGridLocation(start.getX(), windowParams.getPixelOffsetX()), 
					coordinateToGridLocation(start.getY(), windowParams.getPixelOffsetY()), 
					coordinateToGridLocation(end.getX(), windowParams.getPixelOffsetX()), 
					coordinateToGridLocation(end.getY(), windowParams.getPixelOffsetY()));*/
			if(start.getX() == end.getX()) {
				int xValue = coordinateToGridLocation(start.getX(), wall.getWindowParams().getPixelOffsetX(), wall.getWindowParams().getPixelScale());
				int startYValue = coordinateToGridLocation(start.getY(), wall.getWindowParams().getPixelOffsetY(), wall.getWindowParams().getPixelScale());
				int endYValue = coordinateToGridLocation(end.getY(), wall.getWindowParams().getPixelOffsetY(), wall.getWindowParams().getPixelScale());
				
				if(startYValue < endYValue) {
					for(int i = startYValue; i <= endYValue; i++) {
						oltObj.addStationaryObject(wall, xValue, i);
					}
				} else {
					for(int i = endYValue; i <= startYValue; i++) {
						oltObj.addStationaryObject(wall, xValue, i);
					}
				}
			} else if(start.getY() == end.getY()) {
				int yValue = coordinateToGridLocation(start.getY(), wall.getWindowParams().getPixelOffsetY(), wall.getWindowParams().getPixelScale());
				int startXValue = coordinateToGridLocation(start.getX(), wall.getWindowParams().getPixelOffsetX(), wall.getWindowParams().getPixelScale());
				int endXValue = coordinateToGridLocation(end.getX(), wall.getWindowParams().getPixelOffsetX(), wall.getWindowParams().getPixelScale());
				
				if(startXValue < endXValue) {
					for(int i = startXValue; i <= endXValue; i++) {
						oltObj.addStationaryObject(wall, i, yValue);
					}
				} else {
					for(int i = endXValue; i <= startXValue; i++) {
						oltObj.addStationaryObject(wall, i, yValue);
					}
				}
			} else {
				System.err.println("Error adding obstacle to grid.");
				System.exit(1);
			}
			
			start = end;
		}
	}
	
	
	/*
	 * Helper function that translates pixel coordinate to the corresponding
	 * grid location.
	 * 
	 */
	private int coordinateToGridLocation(int value, int offset, int scale) {
		return (value - offset) / scale;
	}
	
	//////////////////////////////////////////////////////////
	// Private member variables
	//////////////////////////////////////////////////////////
	
	private static MazeBuilder instance = null;

}
